#ifndef _UTILS_H
#define _UTILS_H

#include "sparse_array.h"
#include "param.h"

#include <iostream>
#include <set>

//
// An abstract data class
//
class DataSet
{
    public:
        DataSet(unsigned int *Y, int l) : _length(l), _Y(Y) {}
        virtual ~DataSet() { delete _Y; }
        virtual int length() const { return _length; }
        virtual int label( int i ) const { return _Y[i]; }
    protected:
        int _length;
        unsigned int *_Y;
};

class MultiDataSet: public DataSet
{
    public:
        MultiDataSet(SparseArray **v, std::string *s, SparseArray **t, unsigned int *labels, int l) 
            : DataSet(labels,l), vectors(v), strings(s), trees(t), _chars(0) 
        {
            got_vectors = (v != 0);
            got_strings = (s != 0);
            got_trees = (t != 0);
        }
        virtual ~MultiDataSet() 
        { 
            delete [] strings; 
//            for (int i=0; i < _length; ++i)
//                delete vectors[i];
            delete vectors; 
            delete trees;
        }

        std::string string( int i ) const 
        { 
            if (got_strings) return strings[i]; 
            return "";
        }

        SparseArray *vector( int i ) const 
        { 
            if (got_vectors) return vectors[i]; 
            return 0; 
        }

        SparseArray *tree( int i ) const 
        { 
            if (got_trees) return trees[i]; 
            return 0;
        }

        void set_chars(int c) { _chars=c;}
        int chars() const { return _chars;}

    public:
        bool got_vectors;
        bool got_strings;
        bool got_trees;

    private:
        SparseArray **vectors;
        std::string *strings;
        SparseArray **trees;
        int _chars;
};

class SequenceDataSet
{
    public:
        SequenceDataSet(std::vector<SparseArray*> *sequences, 
                        std::vector<unsigned int> *labels, int s, int k) 
            : m_labels(labels), m_sequences(sequences), m_num_sequences(s), m_klasses(k)
        {}

        virtual ~SequenceDataSet() 
        { 
            delete [] m_sequences; 
            delete [] m_labels;
        }

        std::vector<SparseArray*> &sequence( int i ) const 
        { return m_sequences[i]; }

        std::vector<unsigned int> &label(int i) const { return m_labels[i]; }
        MultiDataSet *flatten();
        SparseArray* add_context(int sequence, int instance, int order=1);
        SparseArray* add_context(int sequence, int instance, std::vector<unsigned int> &labels, 
                                 int order=1);
                                 
        static SparseArray* add_context(SparseArray *instance, int label, unsigned int klasses);
        static SparseArray* add_context(SparseArray *instance, std::vector<unsigned int> &labels, 
                                        unsigned int klasses, int order=1);

    private:
        std::vector<unsigned int> *m_labels;
        std::vector<SparseArray*> *m_sequences;
        int m_num_sequences, m_klasses;
};

std::map<std::string,std::set<int> > &parent_class_sets();
std::string parent_class(const std::string &tag);

inline void pp(const std::string &message, bool clear=false)
{
    static int mem=0;

    if(!clear)
        for(int i=0; i < mem; ++i)
            std::cout << "\b \b";
    std::cout << message;
    std::cout.flush();
    
    if(clear)
        mem = 0;
    else
        mem = message.length();
}

inline void print_matrix(SparseMatrix2d &x)
{
    for(int i=0; i<x.rows(); ++i)
    {
        for(int j=0; j<x.columns(); ++j)
            std::cout << '\t' << x(i,j);
        std::cout << std::endl;
    }
}

// reading data files
bool load_multi_data(char *vector_file, char *string_file, char *tree_file, 
                     MultiDataSet *&data, Parameter &param);
bool load_sequence_data(char *sequence_file, SequenceDataSet *&data, Parameter &param, bool no_add=false);

// splitting datasets into train/test sets
bool get_heldout_set(int fold, MultiDataSet *data, MultiDataSet *&train_data, 
                     MultiDataSet *&heldout_data, Parameter *param);
bool get_heldout_sequences(int fold, SequenceDataSet *data, SequenceDataSet *&train_data, 
                     SequenceDataSet *&heldout_data, Parameter *param, GPParameter *ho_param);

// map numeric class values to tag strings
std::string map_class(unsigned int cls);

// serialise an array of doubles
inline std::ostream& serialise1d(std::ostream &os, const double *data, const int length)
{
    for( int i=0; i < length; ++i)
        os << " " << data[i];
    os << std::endl;

    return os;
}

// deserialise an array of doubles
inline std::istream& deserialise1d(std::istream &os, double *&data, int &length)
{
    std::vector<double> buffer;
    double tmp=0;
    while(!os.eof())
    {
        os >> tmp;
        if (os.eof()) break;
        buffer.push_back(tmp);
    }

    length = buffer.size();
    data = new double [length];
    for(int i=0; i < length; ++i)
        data[i] = buffer[i];

    return os;
}

#endif // _UTILS_H
